/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <melon/json/dynamic_converter.h>

#include <algorithm>
#include <map>
#include <optional>
#include <vector>

#include <melon/expected.h>
#include <melon/km_vector.h>
#include <melon/optional.h>
#include <melon/portability/gtest.h>

MELON_GNU_DISABLE_WARNING("-Wdeprecated-declarations")

namespace melon {
    namespace {
        // static tests

        using namespace dynamicconverter_detail;

        struct Foo {
        };

        static_assert(!is_container<int>::value);
        static_assert(!is_container<std::pair<int, int> >::value);
        static_assert(!is_container<Foo>::value);
        static_assert(!class_is_container<Foo>::value);

        static_assert(is_container<std::vector<int> >::value);
        static_assert(is_container<std::set<int> >::value);
        static_assert(is_container<std::map<int, int> >::value);
        static_assert(class_is_container<std::vector<Foo> >::value);

        static_assert(!is_map<int>::value);
        static_assert(!is_map<std::set<int> >::value);

        static_assert(is_map<std::map<int, int> >::value);

        static_assert(!is_range<int>::value);

        static_assert(is_range<std::set<int> >::value);
        static_assert(is_range<std::vector<int> >::value);

        static_assert(is_like_pointer<Expected<int, std::nullptr_t> >::value);
        static_assert(is_like_pointer<std::optional<int> >::value);
        static_assert(is_like_pointer<melon::Optional<int> >::value);
        static_assert(is_like_pointer<int *>::value);
        static_assert(is_like_pointer<std::unique_ptr<int> >::value);

        static_assert(is_optional<Expected<int, std::nullptr_t> >::value);
        static_assert(is_optional<std::optional<int> >::value);
        static_assert(is_optional<melon::Optional<int> >::value);
        static_assert(!is_optional<int *>::value);
        static_assert(!is_optional<std::unique_ptr<int> >::value);
    } // namespace

    TEST(DynamicConverter, dynamics) {
        Dynamic d1 = 12;
        auto d2 = convertTo<Dynamic>(d1);
        EXPECT_EQ(12, d2.getInt());

        Dynamic d3 = Dynamic::array(1, 2, 3);
        auto v = convertTo<std::vector<Dynamic> >(d3);
        EXPECT_EQ(1, v.at(0).getInt());
        EXPECT_EQ(2, v.at(1).getInt());
        EXPECT_EQ(3, v.at(2).getInt());

        Dynamic d4 = Dynamic::object("1", 1)("2", 2.3);
        auto m = convertTo<std::map<std::string, Dynamic> >(d4);
        EXPECT_EQ(1, m.at("1").getInt());
        EXPECT_EQ(2.3, m.at("2").getDouble());
    }

    TEST(DynamicConverter, arithmeticTypes) {
        Dynamic d1 = 12;
        auto i1 = convertTo<int>(d1);
        EXPECT_EQ(i1, 12);

        Dynamic d2 = 123456789012345;
        auto i2 = convertTo<int64_t>(d2);
        EXPECT_EQ(i2, 123456789012345);

        Dynamic d4 = 3.141;
        auto i4 = convertTo<float>(d4);
        EXPECT_EQ((int)(i4 * 100), 314);

        Dynamic d5 = true;
        auto i5 = convertTo<bool>(d5);
        EXPECT_EQ(i5, true);

        Dynamic d6 = 15;
        const auto i6 = convertTo<const int>(d6);
        EXPECT_EQ(i6, 15);

        Dynamic d7 = "87";
        auto i7 = convertTo<int>(d7);
        EXPECT_EQ(i7, 87);

        Dynamic d8 = "false";
        auto i8 = convertTo<bool>(d8);
        EXPECT_EQ(i8, false);
    }

    TEST(DynamicConverter, enums) {
        enum enum1 { foo = 1, bar = 2 };

        Dynamic d1 = 1;
        auto i1 = convertTo<enum1>(d1);
        EXPECT_EQ(i1, foo);

        Dynamic d2 = 2;
        auto i2 = convertTo<enum1>(d2);
        EXPECT_EQ(i2, bar);

        enum class enum2 { FOO = 1, BAR = 2 };

        Dynamic d3 = 1;
        auto i3 = convertTo<enum2>(d3);
        EXPECT_EQ(i3, enum2::FOO);

        Dynamic d4 = 2;
        auto i4 = convertTo<enum2>(d4);
        EXPECT_EQ(i4, enum2::BAR);
    }

    TEST(DynamicConverter, simpleBuiltins) {
        Dynamic d1 = "Haskell";
        auto i1 = convertTo<melon::kmstring>(d1);
        EXPECT_EQ(i1, "Haskell");

        Dynamic d2 = 13;
        auto i2 = convertTo<std::string>(d2);
        EXPECT_EQ(i2, "13");

        Dynamic d3 = Dynamic::array(12, "Scala");
        auto i3 = convertTo<std::pair<int, std::string> >(d3);
        EXPECT_EQ(i3.first, 12);
        EXPECT_EQ(i3.second, "Scala");

        Dynamic d4 = Dynamic::object("C", "C++");
        auto i4 = convertTo<std::pair<std::string, melon::kmstring> >(d4);
        EXPECT_EQ(i4.first, "C");
        EXPECT_EQ(i4.second, "C++");
    }

    TEST(DynamicConverter, optional) {
        using Optionals = std::pair<std::optional<int>, Optional<int> >;
        const Dynamic null;
        EXPECT_TRUE(Optionals(4, 5) == convertTo<Optionals>(Dynamic::array(4, 5)));
        EXPECT_TRUE(Optionals() == convertTo<Optionals>(Dynamic::array(null, null)));
        EXPECT_TRUE(toDynamic(Optionals(4, 5)) == Dynamic::array(4, 5));
        EXPECT_TRUE(toDynamic(Optionals()) == Dynamic::array(null, null));
    }

    TEST(DynamicConverter, pointer) {
        const int x = 7;
        using Pointers = std::pair<const int *, int *>;
        const Dynamic null;
        EXPECT_TRUE(toDynamic(Pointers(&x, nullptr)) == Dynamic::array(7, null));
        // Cannot convert to pointers.
    }

    TEST(DynamicConverter, simpleKmvector) {
        Dynamic d1 = Dynamic::array(1, 2, 3);
        auto i1 = convertTo<melon::kmvector<int> >(d1);
        decltype(i1) i1b = {1, 2, 3};
        EXPECT_EQ(i1, i1b);
    }

    TEST(DynamicConverter, simpleContainer) {
        Dynamic d1 = Dynamic::array(1, 2, 3);
        auto i1 = convertTo<std::vector<int> >(d1);
        decltype(i1) i1b = {1, 2, 3};
        EXPECT_EQ(i1, i1b);

        Dynamic d2 = Dynamic::array(1, 3, 5, 2, 4);
        auto i2 = convertTo<std::set<int> >(d2);
        decltype(i2) i2b = {1, 2, 3, 5, 4};
        EXPECT_EQ(i2, i2b);
    }

    TEST(DynamicConverter, simpleMap) {
        Dynamic d1 = Dynamic::object(1, "one")(2, "two");
        auto i1 = convertTo<std::map<int, std::string> >(d1);
        decltype(i1) i1b = {{1, "one"}, {2, "two"}};
        EXPECT_EQ(i1, i1b);

        Dynamic d2 =
                Dynamic::array(Dynamic::array(3, "three"), Dynamic::array(4, "four"));
        auto i2 = convertTo<std::unordered_map<int, std::string> >(d2);
        decltype(i2) i2b = {{3, "three"}, {4, "four"}};
        EXPECT_EQ(i2, i2b);
    }

    TEST(DynamicConverter, mapKeyedByString) {
        Dynamic d1 = Dynamic::object("1", "one")("2", "two");
        auto i1 = convertTo<std::map<std::string, std::string> >(d1);
        decltype(i1) i1b = {{"1", "one"}, {"2", "two"}};
        EXPECT_EQ(i1, i1b);

        Dynamic d2 =
                Dynamic::array(Dynamic::array("3", "three"), Dynamic::array("4", "four"));
        auto i2 = convertTo<std::unordered_map<std::string, std::string> >(d2);
        decltype(i2) i2b = {{"3", "three"}, {"4", "four"}};
        EXPECT_EQ(i2, i2b);
    }

    TEST(DynamicConverter, mapToVectorOfPairs) {
        Dynamic d1 = Dynamic::object("1", "one")("2", "two");
        auto i1 = convertTo<std::vector<std::pair<std::string, std::string> > >(d1);
        std::sort(i1.begin(), i1.end());
        decltype(i1) i1b = {{"1", "one"}, {"2", "two"}};
        EXPECT_EQ(i1, i1b);
    }

    TEST(DynamicConverter, nestedContainers) {
        Dynamic d1 =
                Dynamic::array(Dynamic::array(1), Dynamic::array(), Dynamic::array(2, 3));
        auto i1 = convertTo<melon::kmvector<std::vector<uint8_t> > >(d1);
        decltype(i1) i1b = {{1}, {}, {2, 3}};
        EXPECT_EQ(i1, i1b);

        Dynamic h2a = Dynamic::array("3", ".", "1", "4");
        Dynamic h2b = Dynamic::array("2", ".", "7", "2");
        Dynamic d2 = Dynamic::object(3.14, h2a)(2.72, h2b);
        auto i2 = convertTo<std::map<double, std::vector<melon::kmstring> > >(d2);
        decltype(i2) i2b = {
            {3.14, {"3", ".", "1", "4"}},
            {2.72, {"2", ".", "7", "2"}},
        };
        EXPECT_EQ(i2, i2b);
    }

    struct A {
        int i;
        bool operator==(const A &o) const { return i == o.i; }
    };

    template<>
    struct DynamicConverter<A> {
        static A convert(const Dynamic &d) { return {convertTo<int>(d["i"])}; }
    };

    TEST(DynamicConverter, customClass) {
        Dynamic d1 = Dynamic::object("i", 17);
        auto i1 = convertTo<A>(d1);
        EXPECT_EQ(i1.i, 17);

        Dynamic d2 =
                Dynamic::array(Dynamic::object("i", 18), Dynamic::object("i", 19));
        auto i2 = convertTo<std::vector<A> >(d2);
        decltype(i2) i2b = {{18}, {19}};
        EXPECT_EQ(i2, i2b);
    }

    TEST(DynamicConverter, crazy) {
        // we are going to create a vector<unordered_map<bool, T>>
        // we will construct some of the maps from Dynamic objects,
        //   some from a vector of KV pairs.
        // T will be vector<set<string>>

        std::set<std::string> s1 = {"a", "e", "i", "o", "u"};
        std::set<std::string> s2 = {"2", "3", "5", "7"};
        std::set<std::string> s3 = {"Hello", "World"};

        std::vector<std::set<std::string> > v1 = {};
        std::vector<std::set<std::string> > v2 = {s1, s2};
        std::vector<std::set<std::string> > v3 = {s3};

        std::unordered_map<bool, std::vector<std::set<std::string> > > m1 = {
            {true, v1}, {false, v2}
        };
        std::unordered_map<bool, std::vector<std::set<std::string> > > m2 = {
            {true, v3}
        };

        std::vector<std::unordered_map<bool, std::vector<std::set<std::string> > > > f1 =
                {m1, m2};

        Dynamic ds1 = Dynamic::array("a", "e", "i", "o", "u");
        Dynamic ds2 = Dynamic::array("2", "3", "5", "7");
        Dynamic ds3 = Dynamic::array("Hello", "World");

        Dynamic dv1 = Dynamic::array;
        Dynamic dv2 = Dynamic::array(ds1, ds2);
        Dynamic dv3(Dynamic::array(ds3));

        Dynamic dm1 = Dynamic::object(true, dv1)(false, dv2);
        Dynamic dm2 = Dynamic::array(Dynamic::array(true, dv3));

        Dynamic df1 = Dynamic::array(dm1, dm2);

        auto i = convertTo<std::vector<
            std::unordered_map<bool, std::vector<std::set<std::string> > > > >(
            df1); // yes, that is 5 close-chevrons

        EXPECT_EQ(f1, i);
    }

    TEST(DynamicConverter, consts) {
        Dynamic d1 = 7.5;
        auto i1 = convertTo<const double>(d1);
        EXPECT_EQ(7.5, i1);

        Dynamic d2 = "Hello";
        auto i2 = convertTo<const std::string>(d2);
        decltype(i2) i2b = "Hello";
        EXPECT_EQ(i2b, i2);

        Dynamic d3 = true;
        auto i3 = convertTo<const bool>(d3);
        EXPECT_TRUE(i3);

        Dynamic d4 = "true";
        auto i4 = convertTo<const bool>(d4);
        EXPECT_TRUE(i4);

        Dynamic d5 = Dynamic::array(1, 2);
        auto i5 = convertTo<const std::pair<const int, const int>>(d5);
        decltype(i5) i5b = {1, 2};
        EXPECT_EQ(i5b, i5);
    }

    struct Token {
        int kind_;
        kmstring lexeme_;

        explicit Token(int kind, const kmstring &lexeme)
            : kind_(kind), lexeme_(lexeme) {
        }
    };

    template<>
    struct DynamicConverter<Token> {
        static Token convert(const Dynamic &d) {
            int k = convertTo<int>(d["KIND"]);
            kmstring lex = convertTo<kmstring>(d["LEXEME"]);
            return Token(k, lex);
        }
    };

    TEST(DynamicConverter, example) {
        Dynamic d1 = Dynamic::object("KIND", 2)("LEXEME", "a token");
        auto i1 = convertTo<Token>(d1);
        EXPECT_EQ(i1.kind_, 2);
        EXPECT_EQ(i1.lexeme_, "a token");
    }

    TEST(DynamicConverter, construct) {
        using std::map;
        using std::pair;
        using std::string;
        using std::vector; {
            vector<int> c{1, 2, 3};
            Dynamic d = Dynamic::array(1, 2, 3);
            EXPECT_EQ(d, toDynamic(c));
        } {
            vector<float> c{1.0f, 2.0f, 4.0f};
            Dynamic d = Dynamic::array(1.0, 2.0, 4.0);
            EXPECT_EQ(d, toDynamic(c));
        } {
            map<int, int> c{{2, 4}, {3, 9}};
            Dynamic d = Dynamic::object(2, 4)(3, 9);
            EXPECT_EQ(d, toDynamic(c));
        } {
            map<string, string> c{{"a", "b"}};
            Dynamic d = Dynamic::object("a", "b");
            EXPECT_EQ(d, toDynamic(c));
        } {
            map<string, pair<string, int> > c{{"a", {"b", 3}}};
            Dynamic d = Dynamic::object("a", Dynamic::array("b", 3));
            EXPECT_EQ(d, toDynamic(c));
        } {
            map<string, pair<string, int> > c{{"a", {"b", 3}}};
            Dynamic d = Dynamic::object("a", Dynamic::array("b", 3));
            EXPECT_EQ(d, toDynamic(c));
        } {
            vector<int> vi{2, 3, 4, 5};
            auto c = std::make_pair(
                range(vi.begin(), vi.begin() + 3),
                range(vi.begin() + 1, vi.begin() + 4));
            Dynamic d =
                    Dynamic::array(Dynamic::array(2, 3, 4), Dynamic::array(3, 4, 5));
            EXPECT_EQ(d, toDynamic(c));
        } {
            vector<Optional<int> > c{3, none, 4};
            Dynamic d = Dynamic::array(3, Dynamic(), 4);
            EXPECT_EQ(d, toDynamic(c));
        } {
            vector<bool> vb{true, false};
            Dynamic d = Dynamic::array(true, false);
            EXPECT_EQ(d, toDynamic(vb));
        } {
            enum enum1 { foo = 1, bar = 2 };

            Dynamic d1 = 1;
            EXPECT_EQ(d1, toDynamic(enum1::foo));

            Dynamic d2 = 2;
            EXPECT_EQ(d2, toDynamic(enum1::bar));

            enum class enum2 : char { FOO = 'a', BAR = 'b' };

            Dynamic d3 = 'a';
            EXPECT_EQ(d3, toDynamic(enum2::FOO));

            Dynamic d4 = 'b';
            EXPECT_EQ(d4, toDynamic(enum2::BAR));
        }
    }

    TEST(DynamicConverter, errors) {
        const auto int32Over =
                static_cast<int64_t>(std::numeric_limits<int32_t>().max()) + 1;
        const auto floatOver =
                static_cast<double>(std::numeric_limits<float>().max()) * 2;

        Dynamic d1 = int32Over;
        EXPECT_THROW(convertTo<int32_t>(d1), std::range_error);

        Dynamic d2 = floatOver;
        EXPECT_THROW(convertTo<float>(d2), std::range_error);
    }

    TEST(DynamicConverter, partialDynamics) {
        std::vector<Dynamic> c{
            Dynamic::array(2, 3, 4),
            Dynamic::array(3, 4, 5),
        };
        Dynamic d = Dynamic::array(Dynamic::array(2, 3, 4), Dynamic::array(3, 4, 5));
        EXPECT_EQ(d, toDynamic(c));

        std::unordered_map<std::string, Dynamic> m{{"one", 1}, {"two", 2}};
        Dynamic md = Dynamic::object("one", 1)("two", 2);
        EXPECT_EQ(md, toDynamic(m));
    }

    TEST(DynamicConverter, asanExceptionCaseUmap) {
        EXPECT_THROW(
            (convertTo<std::unordered_map<int, int>>(Dynamic::array(1))), TypeError);
    }

    TEST(DynamicConverter, asanExceptionCaseUset) {
        EXPECT_THROW(
            (convertTo<std::unordered_set<int>>(
                Dynamic::array(1, Dynamic::array(), 3))),
            TypeError);
    }

    static int constructB = 0;
    static int destroyB = 0;
    static int ticker = 0;

    struct B {
        struct BException : std::exception {
        };

        /* implicit */
        B(int x) : x_(x) {
            if (ticker-- == 0) {
                throw BException();
            }
            constructB++;
        }

        B(const B &o) : x_(o.x_) { constructB++; }
        ~B() { destroyB++; }
        int x_;
    };

    template<>
    struct DynamicConverter<B> {
        static B convert(const Dynamic &d) { return B(convertTo<int>(d)); }
    };

    TEST(DynamicConverter, doubleDestroy) {
        Dynamic d = Dynamic::array(1, 3, 5, 7, 9, 11, 13, 15, 17);
        ticker = 3;

        EXPECT_THROW(convertTo<std::vector<B>>(d), B::BException);
        EXPECT_EQ(constructB, destroyB);
    }

    TEST(DynamicConverter, simpleVectorBool) {
        std::vector<bool> bools{true, false};
        auto d = toDynamic(bools);
        auto actual = convertTo<decltype(bools)>(d);
        EXPECT_EQ(bools, actual);
    }
} // namespace melon
